/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bakenote.ui;

import bakenote.core.Amount;
import bakenote.core.Formula;
import bakenote.core.Ingredient;
import bakenote.core.Recipe;
import org.apache.pivot.beans.BXML;
import org.apache.pivot.beans.Bindable;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.List;
import org.apache.pivot.collections.List.ItemIterator;
import org.apache.pivot.collections.Map;
import org.apache.pivot.util.Resources;
import org.apache.pivot.wtk.Button;
import org.apache.pivot.wtk.*;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.Insets;
import org.apache.pivot.wtk.Menu;
import org.apache.pivot.wtk.content.TableViewNumberCellRenderer;
import org.apache.pivot.wtk.content.TableViewRowEditor;
import org.apache.pivot.wtk.content.TableViewTextAreaCellRenderer;
import org.apache.pivot.wtk.skin.terra.TerraTheme;
import org.apache.pivot.wtk.validation.DoubleValidator;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.awt.*;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author lello
 */
public class IngredientEditorPane extends TablePane implements Bindable {

    private static int DEF_NUM_INGREDIENTS = 40;
    private static List<Ingredient> ingredientList = new ArrayList<Ingredient>();
    private Recipe newRecipe = null;
    private Recipe editedRecipe = null;
    @BXML
    private static TableView ingredientsTableView = null;
    @BXML
    private static TextInput ingredientNameTextInput = null;
    @BXML
    private static PushButton moveUpIngredientButton = null;
    @BXML
    private static PushButton moveDownIngredientButton = null;
    @BXML
    private static PushButton addIngredientButton = null;
    @BXML
    private static PushButton removeIngredientButton = null;
    //private static ScriptEngineManager factory = new ScriptEngineManager();
    // I know this is expensive but it's so easy to parse math in the table...
    private static ScriptEngine engine = (new ScriptEngineManager()).getEngineByName("JavaScript");

    public IngredientEditorPane() {
        ingredientList.clear();
    }

    private MenuHandler ingredientMenuHandler = new MenuHandler.Adapter() {

        @Override
        public boolean configureContextMenu(Component cmpnt, Menu menu, int x, int y) {

                TableView tableView = (TableView) cmpnt;
                int rowIndex = tableView.getRowAt(y);
                final Ingredient ingredient = (Ingredient) tableView.getTableData().get(rowIndex);

                if (ingredient.getAmount().isMeasurable()) {
                    // Create the context menu
                    Menu.Section menuSection = new Menu.Section();
                    final Menu.Item isReferenceMenuItem = new Menu.Item("reference");
                    final Menu.Item isNotReferenceMenuItem = new Menu.Item("not a reference");

                    isReferenceMenuItem.setToggleButton(true);
                    isNotReferenceMenuItem.setToggleButton(true);

                    ButtonGroup buttonGroup = new ButtonGroup();
                    isReferenceMenuItem.setButtonGroup(buttonGroup);
                    isNotReferenceMenuItem.setButtonGroup(buttonGroup);

                    boolean selected = false;
                    if (ingredient.isReference()) {
                       selected = true;
                    }
                    isReferenceMenuItem.setSelected(selected);
                    isNotReferenceMenuItem.setSelected(!selected);

                    menuSection.add(isReferenceMenuItem);
                    menuSection.add(isNotReferenceMenuItem);

                    menu.getSections().add(menuSection);

                    isReferenceMenuItem.setAction(new Action() {
                        @Override
                        public void perform(Component source) {
                            ingredient.setReference(true);
                        }
                    });
                    isNotReferenceMenuItem.setAction(new Action() {
                        @Override
                        public void perform(Component source) {
                            ingredient.setReference(false);
                        }
                    });
                }
            return false;
        }
    };

    public void initialize(Map<String, Object> map, URL url, Resources rsrcs) {

        // Create an empty table with 20 "empty" ingredients
        for (int i = 0; i < DEF_NUM_INGREDIENTS; i++) {
            ingredientList.add(new Ingredient());
        }

        // Create table data model
        ingredientsTableView.setTableData(ingredientList);
        ingredientsTableView.getTableViewSelectionListeners().add(tableViewSelectionListener);
        ingredientsTableView.setMenuHandler(ingredientMenuHandler);

        addIngredientButton.getButtonPressListeners().add(addButtonListener);
        removeIngredientButton.getButtonPressListeners().add(removeButtonListener);
        moveUpIngredientButton.getButtonPressListeners().add(moveUpButtonListener);
        moveDownIngredientButton.getButtonPressListeners().add(moveDownButtonListener);

    }

    /**
     * This method parse the information of the Window and returns
     * a new Recipe().
     *
     * @param asnew
     * @return
     */
    public List<Formula> getRecipeIngredients(boolean asnew) {

        // Parse the table with the ingredients and create a new recipe
        List<Formula> formulaList = new ArrayList<Formula>();
        Formula f = null;
        ItemIterator<Ingredient> itr = (ItemIterator<Ingredient>) ingredientList.iterator();

        while (itr.hasNext()) {
            Ingredient ingr = itr.next();
            if (!ingr.getName().trim().isEmpty()) { // found an ingredient in the table
                if (ingr.getAmount().isNull()) {
                    // create new formula
                    f = new Formula();
                    if (ingr.getName().isEmpty()) {
                        f.setName("MAIN_FORMULA");
                    } else {
                        f.setName(ingr.getName());
                    }
                    formulaList.add(f);
                } else {
                    // if the formulaList is empty add a formula
                    if (formulaList.isEmpty()) {
                        f = new Formula();
                        f.setName("MAIN_FORMULA");
                        formulaList.add(f);
                    }
                    // add new ingredient to the formula
                    Ingredient ingredient = new Ingredient(ingr.getName(), ingr.getAmount(), ingr.getUnit());
                    ingredient.setReference(ingr.isReference());
                    f.addIngredient(ingredient);
                }
            }
        }
        // TODO: perform some consistency check on the ingredients.
        return formulaList;
    }

    /**
     * This is called when an existing recipe is edited
     *
     * @param r
     */
    public void editIngredients(Recipe r) {

        // Cache recipe
        editedRecipe = r;

        int k = -1;
        // TODO: We should reset the list if we reuse the RecipeEditorWindow object.
        // At the moment we create an instance of a recipeEditorWindow every time we edit a recipe
        // so there is no need to reset the list. A better solution would be to reuse the same object
        // and properly clean the list.
        //ingredientList.clear();
        for (Formula f : editedRecipe.getFormulaList()) {
            if (!f.getName().equals("MAIN_FORMULA")) {
                k++;
                ingredientList.insert(new Ingredient(f.getName(), Amount.valueOf("")), k);
            }
            for (Ingredient ing : f.getIngredients()) {
                k++;
                ingredientList.insert(ing, k);
            }
            k++;
        }

    }

    public static class IngredientNameCellRenderer extends TableViewTextAreaCellRenderer {

        public IngredientNameCellRenderer() {
        }

        @Override
        public void render(Object row, int rowIndex, int columnIndex,
                           TableView tableView, String columnName,
                           boolean selected, boolean highlighted, boolean disabled) {

            // First we render as a TextArea, then we change style
            super.render(row, rowIndex, columnIndex, tableView, columnName,
                    selected, highlighted, disabled);

            // Here we apply our custom styles
            if (row != null) {
                Ingredient ing = (Ingredient) row;
                Amount amount = ing.getAmount();
                if (amount != null) {
                    if (amount.isNull()) {
                        getStyles().put("color", 22);
                        //System.out.println(getStyles().get("margin").toString());
                        getStyles().put("margin", new Insets(2, 2, 2, 2));
                        //System.out.println(getStyles().get("font").toString());
                        getStyles().put("font", new Font("Verdana", Font.BOLD, 14));
                    } else {
                        //getStyles().put("margin", new Insets(2,20,2,2));
                        getStyles().put("margin", new Insets(2, 10, 2, 2));
                    }
                }
            }
        }
    }

    public static class AmountCellRenderer extends TableViewNumberCellRenderer {

        //private static NumberFormat nfi = NumberFormat.getInstance(Locale.ITALIAN);
        private static DecimalFormat nfi = new DecimalFormat("0.0");

        public AmountCellRenderer() {
            nfi.setMaximumFractionDigits(2);
            nfi.setMinimumFractionDigits(2);
            nfi.setDecimalSeparatorAlwaysShown(true);
            this.setNumberFormat(nfi);
        }

        //public AmountCellRenderer() { }
        @Override
        public String toString(Object row, String columnName) {
            Ingredient ing = (Ingredient) row;
            Amount amount = ing.getAmount();
            if (!amount.isNull()) {
                if (amount.isMeasurable()) {
                    //System.out.println("amount mes " + amount.toString() + amount.isMeasurable());
                    //return super.toString(row, columnName);
                    return nfi.format(amount.getDouble());
                } else {
                    //System.out.println("amount no mes" + amount.toString() + amount.isMeasurable());
                    return amount.getSymbol();
                }
            } else {
                //System.out.println("amount mes "+amount.toString()+amount.isMeasurable());
                return "";
            }
        }
    }

    public static class UnitsCellRenderer extends TableViewTextAreaCellRenderer {

        public UnitsCellRenderer() {
        }

        @Override
        public String toString(Object row, String columnName) {
            Ingredient ing = (Ingredient) row;
            Amount amount = ing.getAmount();
            if (!amount.isMeasurable()) {
                return "";
            } else {
                //System.out.println(super.toString());
                //return super.toString(row, columnName);
                return ing.getUnit();
            }
        }
    }

    private static class AmountTextInputMapping implements TextInput.TextBindMapping {

        //private static NumberFormat nf = NumberFormat.getInstance(Locale.ITALIAN);
        //private static NumberFormat amountFormat = NumberFormat.getInstance(Locale.ITALIAN);
        //private static DecimalFormat nf = (DecimalFormat) NumberFormat.getInstance();
        //private static DecimalFormat amountFormat = (DecimalFormat) NumberFormat.getInstance();
        private static DecimalFormat amountFormat = new DecimalFormat("#.##");
        //private TextInput ti;
        //private TableView tv;

        //public AmountTextInputMapping(TableView tv, TextInput ti) {
        public AmountTextInputMapping(TextInput ti) {
            //this.tv = tv;
            //this.ti = ti;
            //amountFormat.setMinimumFractionDigits(2);
            //amountFormat.setMaximumFractionDigits(2);
        }

        @Override
        public String toString(Object value) {
            //System.out.println("amount mapping toString" + value);
            //amountFormat = NumberFormat.getInstance(Locale.ITALIAN);
            //int index = tv.getSelectedIndex();
            //System.out.println("index table "+index);
            //if (!((Ingredient)tv.getTableData().get(index)).getAmount().isMeasurable()) {
            Amount am = (Amount) value;
            if (am.isMeasurable()) {
                //System.out.println("amount mapping format measurable " + am.getDouble());
                return amountFormat.format(am.getDouble());
            } else {
                return value.toString();
            }
        }

        @Override
        public Object valueOf(String text) {

            if (text.startsWith("=")) {
                try {
                    String math = text.trim().substring(1);
                    System.out.println("math " + math);
                    return engine.eval(math);
                } catch (ScriptException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }
            }
            if (text.equals("t.t.") || text.equals("q.b.") || text.isEmpty() || text.equals("-")) {
                //System.out.println("amount mapping valueOf verify " + text);
                return text;
            } else {
                try {
                    //System.out.println("amount mapping valueOf not verify " + text);
                    return amountFormat.parse(text).doubleValue();
                } catch (ParseException ex) {
                    Logger.getLogger(AmountTextInputMapping.class.getName()).log(Level.SEVERE, null, ex);
                    return text;
                }
            }
        }
    }

    private static class AmountValidator extends DoubleValidator {

        private static DecimalFormat amountFormat = new DecimalFormat("#.##");
        private double min, max;

        public AmountValidator(double min, double max) {
            this.min = min;
            this.max = max;
        }

        @Override
        public boolean isValid(String text) {

            boolean valid = false;

            // This validate a regular mathematical expression
            if (text.startsWith("=")) {
                String math = text.substring(1);
                String numberRegex = "[+]?[0-9]+(\\.[0-9]*)?";
                String operatorRegex = "[+-/\\*]";
                String expressionRegex = numberRegex+"("+operatorRegex+numberRegex+")*?";
                System.out.println("regex "+expressionRegex);
                valid = math.matches(expressionRegex);
                return valid;
            }

            if (text.equals("t.t.") || text.equals("q.b.") || text.isEmpty())  {
                valid = true;
            } else {
                // Here we force d > min. The default DoubleRangeValidator uses the condition d >= min
                valid = super.isValid(text);
                if (valid) {
                    try {
                        //valid = valid && (parseNumber(text).doubleValue() > min);
                        //valid = valid && (NumberFormat.getInstance().parse(text).doubleValue() > min);
                        valid = valid && (amountFormat.parse(text).doubleValue() > min);
                        //valid = valid && (DecimalFormat.getInstance().parse(text).doubleValue() > min);
                    } catch (ParseException ex) {
                        Logger.getLogger(IngredientEditorPane.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
            return valid;
        }
    }

    public static class IngredientRowEditor extends TableViewRowEditor {

        private static TextInput ti = new TextInput();
        private static TerraTheme theme = (TerraTheme) Theme.getTheme();
        private static AmountValidator drvalidator = new AmountValidator(0.0d, 1e10);

        public IngredientRowEditor() {
            ti.getStyles().put("font", new Font(theme.getFont().getName(), Font.PLAIN, 14));
            ti.setTextKey("amount");
            ti.setTextBindMapping(new AmountTextInputMapping(ti));
            ti.setValidator(drvalidator);
            this.getCellEditors().put("amount", ti);
        }

        @Override
        public boolean keyPressed(int keyCode, Keyboard.KeyLocation kl) {
            boolean consumed = false;
            // Change row only if the text is valid.
            // The endEdit() method calls store()
            // which gives an error is the text inserted in not valid.
            if (((TextInput) getCellEditors().get("amount")).isTextValid()) {
                if (keyCode == Keyboard.KeyCode.ENTER) {
                    endEdit(true);
                    consumed = true;
                } else if (keyCode == Keyboard.KeyCode.ESCAPE) {
                    endEdit(false);
                    consumed = true;
                }
                if (keyCode == Keyboard.KeyCode.DOWN) {
                    //System.out.println("Down pressed");
                    consumed = true;
                    //if (isEditing()) endEdit(true);
                    int index = getTableView().getSelectedIndex();
                    if (index > -1 && index < getTableView().getTableData().getLength() - 1) {
                        getTableView().setSelectedIndex(index + 1);
                    }
                } else if (keyCode == Keyboard.KeyCode.UP) {
                    consumed = true;
                    int index = getTableView().getSelectedIndex();
                    if (index > 0) {
                        getTableView().setSelectedIndex(index - 1);
                    }
                }
            }
            return consumed;
        }
    }

    private static final ButtonPressListener addButtonListener = new ButtonPressListener() {

        @Override
        public void buttonPressed(Button button) {
            int sel = ingredientsTableView.getSelectedIndex();
            if (sel >= 0 && sel < ingredientList.getLength()) {
                ingredientList.insert(new Ingredient(), sel);
            }
        }
    };
    private static final ButtonPressListener removeButtonListener = new ButtonPressListener() {

        @Override
        public void buttonPressed(Button button) {
            int sel = ingredientsTableView.getSelectedIndex();
            if (sel >= 0 && sel < ingredientList.getLength()) {
                //System.out.println("len " + ingredientList.getLength());
                //System.out.println("sel " + sel);
                ingredientList.remove(sel, 1);
                ingredientsTableView.setSelectedIndex(sel);
            }
        }
    };
    private static final ButtonPressListener moveUpButtonListener = new ButtonPressListener() {

        @Override
        public void buttonPressed(Button button) {
            int sel = ingredientsTableView.getSelectedIndex();
            if (sel >= 1 && sel < ingredientList.getLength()) {
                Ingredient i = ingredientList.get(sel - 1);
                Ingredient j = ingredientList.get(sel);
                ingredientList.update(sel - 1, j);
                ingredientList.update(sel, i);
                ingredientsTableView.setSelectedIndex(sel - 1);
            }
        }
    };
    private static final ButtonPressListener moveDownButtonListener = new ButtonPressListener() {

        @Override
        public void buttonPressed(Button button) {
            int sel = ingredientsTableView.getSelectedIndex();
            if (sel >= 0 && sel < ingredientList.getLength() - 1) {
                Ingredient i = ingredientList.get(sel + 1);
                Ingredient j = ingredientList.get(sel);
                ingredientList.update(sel + 1, j);
                ingredientList.update(sel, i);
                ingredientsTableView.setSelectedIndex(sel + 1);
            }
        }
    };
    private static final TableViewSelectionListener tableViewSelectionListener = new TableViewSelectionListener.Adapter() {

        @Override
        public void selectedRowChanged(TableView tv, Object o) {
            int row = tv.getSelectedIndex();
            //System.out.println("changed selected row "+ row);
            if (row >= 0 && row < tv.getTableData().getLength()) {
                if (tv.getRowEditor().isEditing()) {
                    tv.getRowEditor().endEdit(true);
                }
                if (tv.getRowEditor().isEditing()) {
                    System.out.println("Huston, we have a problem.");
                }
                tv.getRowEditor().beginEdit(tv, row, 0);
            }
        }
    };
}
